package cc.wfu.type.filter;

import cc.wfu.type.common.Response;
import cc.wfu.type.config.HttpAuthSecurity;
import cc.wfu.type.exception.TokenEmptyException;
import cc.wfu.type.util.JwtUtil;
import cc.wfu.type.common.Constants;
import cc.wfu.type.common.SecurityConstant;
import cc.wfu.type.util.RedisUtil;
import cc.wfu.type.util.WebUtils;
import cn.hutool.core.util.ObjUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 *  OncePerRequestFilter : 请求只会经过这个过滤器一次
 */
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Value("${server.servlet.context-path:\\\\}")
    private String contextPath;

    @Resource
    @Lazy
    private JwtUtil jwtUtil;
    @Resource
    @Lazy
    private RedisUtil redisUtil;
    @Lazy
    @Resource
    private HttpAuthSecurity httpAuthSecurity;

    @Autowired
    private AntPathMatcher antPathMatcher;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        try {
            // 1、如果是白名单页则跳过
            if (contextPath.equals("\\\\")) {
                if (Arrays.stream(httpAuthSecurity.matchers()).anyMatch(white -> antPathMatcher.match(white, request.getRequestURI()))) {
                    filterChain.doFilter(request, response);
                    return;
                }
            } else {
                if (Arrays.stream(httpAuthSecurity.matchers()).anyMatch(white -> antPathMatcher.match(white, request.getRequestURI().replace(contextPath, "")))) {
                    filterChain.doFilter(request, response);
                    return;
                }
            }

            // 1.1、获取请求头中的jwt
            String jwtToken = request.getHeader(SecurityConstant.TOKEN_HEADER);
            log.info("token: {}", jwtToken);
            if (ObjUtil.isEmpty(jwtToken)) {
                WebUtils.renderString(response, JSON.toJSONString(
                        Response.builder().data(null).code(HttpStatus.FORBIDDEN).info("token 凭证不能为空！").build()
                ));
                return;
            }
            // 2. 解析 jwt
            Claims claims = jwtUtil.parse(jwtToken);
            String userEntityString = claims.getSubject();
            // 3. 转换类型
            JSONObject jsonObject = JSON.parseObject(userEntityString, JSONObject.class);
            log.info("JSONObject:{}", jsonObject);
            Object username = jsonObject.get("username");
            if (Objects.isNull(username)) {
                throw new JwtException("please provide properties【username(用户名) and password(明文密码)】. subject not contain -> " + jsonObject);
            }
            // 4. 从 redis 中获取 authentication
            String item = Constants.PREFIX + Constants.UNDER_LINE + username.toString();
            User user = (User)redisUtil.hashGet(SecurityConstant.TOKEN_KEY, item);
            // 5. authentication 有效，将其交给 Security，相当于认证成功
            UsernamePasswordAuthenticationToken authenticationToken =
                    // 必须使用三个参数的构造方法 -> 标志用户已认证
                    new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            filterChain.doFilter(request, response);
        } catch (JwtException | TokenEmptyException e) {
            log.error("jwtToken拦截出错：{}", e.getMessage());
            throw e;
        }
    }
}